<template>
  <div
    :class="animate"
    :style="{ height: height, width: width }"
    :id="chartOption.bindingDiv"
    ref="chartDiv"
  />
</template>

<script>
import echarts from "echarts";
require("echarts/theme/macarons"); // echarts theme
import resize from "@/views/dashboard/mixins/resize";

import "../../animate/animate.css";
import { stackedBarBIanalysis } from "@/api/tool/datav/chartBI/chartStackedBarBI";
import { parseQueryString, fIsUrL } from "../../util/urlUtil";
import { chartApiBar, remoteChartApi,chartBIanalysis  } from "@/api/tool/datav/chartApi";
import { linkChart } from "../../util/LinkageChart";
import { addOption } from "../../codegen/codegen";
import { getFormsource } from "@/api/tool/datav/formsource";
import VueEvent from "@/views/tool/datav/VueEvent";
import { websocketCommand } from "../../util/websocket.js";
import { dealWithData,testData } from "../../util/utilFun.js";
const animationDuration = 6000;

// 绘制四边形ABCD
const quadrilateral = echarts.graphic.extendShape({
  shape: {},
  buildPath: (ctx, shape) => {
    ctx
      .moveTo(shape.A[0], shape.A[1])
      .lineTo(shape.B[0], shape.B[1])
      .lineTo(shape.C[0], shape.C[1])
      .lineTo(shape.D[0], shape.D[1])
      .closePath();
  }
});

// 注册
echarts.graphic.registerShape("quadrilateral", quadrilateral);

export default {
  mixins: [resize],
  props: {
    className: {
      type: String,
      default: "chart"
    },
    width: {
      type: String,
      default: "100%"
    },
    height: {
      type: String,
      default: "100%"
    },
    chartOption: {
      type: Object
    },
    drawingList: {
      type: Array
    }
  },
  data() {
    return {
      chart: null,
      //dataOption: this.chartOption,
      timer: "",
      wsObj: null,
      wsFlag: false,
      wsTimer: "",
      wsValue: null,
      animate: this.className
    };
  },
  watch: {
    width() {
      this.$nextTick(() => {
        this.chart.resize();
      });
    },
    height() {
      this.$nextTick(() => {
        this.chart.resize();
      });
    },
    chartOption: {
      deep: true,
      handler(newVal) {
        this.$nextTick(() => {
          this.setOptions(JSON.parse(JSON.stringify(newVal)));
        });
      }
    },
    "chartOption.theme": {
      handler() {
        if (this.chart != null) {
          this.chart.dispose();
          this.chart = null;
          this.initChart();
        }
      }
    },
    "chartOption.timeout": {
      handler() {
        this.chart.dispose();
        this.chart = null;
        this.initChart();
      }
    },
    "chartOption.dataSourceType": {
      handler(value) {
        //切换数据源重新连接
        if (value !== "websocket") {
          this.wsFlag = false;
          //每次切换数据源恢复原来动画
          this.animate = this.className;
        }
        if (value !== "url") {
          clearTimeout(this.timer);
        } 
      }
    },
    "chartOption.interfaceKey": {
      handler(newValue, oldValue) {
        //初始化不创建连接，改变接口地址重新创建
        if (oldValue !== undefined && oldValue != "") {
          this.wsValue = null;
          this.closeWs();
          this.createWs();
          this.animate = this.className;
        }
      }
    },
    wsFlag: {
      handler(value) {
        if (!value) {
          this.wsValue = null;
          this.closeWs();
        }
      }
    },
    className: {
      handler(value) {
        this.animate = value;
      }
    }
  },
  mounted() {
    this.$nextTick(() => {
      this.initChart();
    });
  },
  beforeDestroy() {
    if (!this.chart) {
      return;
    }
    this.chart.dispose();
    this.chart = null;
    clearTimeout(this.timer);
    if (this.wsFlag) {
      this.closeWs();
    }
  },
  methods: {
    initChart() {
      echarts.registerTheme("customTheme", this.chartOption.theme);
      this.chart = echarts.init(this.$el, "customTheme");
     
      //判断是否开启定时器，当是访问url接口时候并且timeout不为0时候开启定时器
      if (this.timer != "") {
        clearTimeout(this.timer);
      }
      if (
        (this.chartOption.dataSourceType == 'url' || this.chartOption.dataSourceType == 'database') &&
        this.chartOption.timeout > 0
      ) {
        let timerTask = () => {
          this.setOptions(JSON.parse(JSON.stringify(this.chartOption)));
          this.timer = setTimeout(() => {
            timerTask();
          }, this.chartOption.timeout);
        };

        timerTask();
      }else{
         this.setOptions(JSON.parse(JSON.stringify(this.chartOption)));
      }
    },
    setOptions: async function(dataOption) {
      //初始化数据
      //如果不是静态数据就调用接口给staticDataValue赋值
      if (dataOption.dataSourceType == "url") {
        let paramJson = parseQueryString(dataOption.requestParameters);

        let interfaceURL = dataOption.interfaceURL;

        dataOption.staticDataValue = await chartApiBar(
          dataOption.requestMethod,
          interfaceURL,
          paramJson,
          dataOption.token
        );
      } else if (dataOption.dataSourceType == "database") {
        if (
          dataOption.database.executeSql != undefined &&
          dataOption.database.executeSql != ""
        ) {
          let result = [];
          if(dataOption.database.sqlType != 'custom'){
          
            result = await stackedBarBIanalysis(dataOption.database);
          }else{
            result = await chartBIanalysis(dataOption.database);
          }
          if (result.code == 200) {
            dataOption.staticDataValue = result.data;
          } else {
            alert("操作数据库错误");
          }
        }
      } else if (dataOption.dataSourceType == "form") {
        if (dataOption.formSource != undefined) {
          let response = await getFormsource(dataOption.formSource.id);
          dataOption.staticDataValue = JSON.parse(response.data.formData);
        }
      } else if (dataOption.dataSourceType == "websocket") {
        if (
          dataOption.interfaceKey != undefined &&
          dataOption.interfaceKey != ""
        ) {
          if (!this.wsFlag) {
            this.wsFlag = true;
            this.wsValue = null;
            this.createWs();
          }
          if (this.wsValue != null) {
            dataOption.staticDataValue = this.wsValue;
          }
        }
      }

      //数据格式转化
      if(dataOption.newDataFormat == true && !(dataOption.dataSourceType == 'database' && dataOption.bi != null)){
        let sortData = dealWithData(dataOption.staticDataValue,'series');

        let staticValue = {};
        let axisDataTest = [];
        let series = [];
        for (const dt of sortData) {
          axisDataTest.push(dt.type)
        }

        for (const object of sortData[0].data) {
          let data = [];    
          sortData.forEach(element => {
            let objList = element.data.filter(item => item.name == object.name)
            data.push(objList[0].value)
          });

          series.push({"name":object.name,"stack":object.type,"data":data})
        
        }
        staticValue.axisData = axisDataTest;
        staticValue.series = series;
        dataOption.staticDataValue = staticValue;
      }


      this.createGraphBySelected(this.chart, dataOption);

      //图例点击事件
      this.chart.on("legendselectchanged", obj => {
        let dataOption = this.chartOption;
        this.createGraphBySelected(this.chart, dataOption, obj.selected);
      });
    },
    //使用series模板,info需要有name,data,color,这三项和tooltip,legend联动
    //info需要有bar的高度，与投影有关的向量OB,OC,三个面的颜色以及A的坐标
    //由于使用echarts的x轴，因此需要调用api.coord来获得x轴离散数据所生成的label的坐标
    //那么A的坐标使用(xOffset,yOffset)，表示A的坐标相对于label的值
    //A的实际坐标为label坐标(api.value(0),0)+A的相对坐标(xOffset,yOffset)
    addThreeDBarSeries(info, barIndex, series) {
      let data = {
        type: "custom",
        data: info[barIndex].data,
        name: info[barIndex].name,
        color: info[barIndex].color,
        renderItem: function(params, api) {
          const location = api.coord([api.value(0), 0]);
          return {
            type: "group",
            children: [
              {
                //left
                type: "quadrilateral",
                shape: {
                  api,
                  A: [
                    location[0] + info[barIndex].xOffset,
                    location[1] +
                      info[barIndex].yOffset[params.dataIndex] -
                      info[barIndex].height[params.dataIndex]
                  ],
                  B: [
                    location[0] + info[barIndex].xOffset + info[barIndex].OC[0],
                    location[1] +
                      info[barIndex].yOffset[params.dataIndex] -
                      info[barIndex].height[params.dataIndex] +
                      info[barIndex].OC[1]
                  ],
                  C: [
                    location[0] + info[barIndex].xOffset + info[barIndex].OC[0],
                    location[1] +
                      info[barIndex].yOffset[params.dataIndex] +
                      info[barIndex].OC[1]
                  ],
                  D: [
                    location[0] + info[barIndex].xOffset,
                    location[1] + info[barIndex].yOffset[params.dataIndex]
                  ]
                },
                style: {
                  fill: info[barIndex].faceColors[0]
                }
              },
              {
                //right
                type: "quadrilateral",
                shape: {
                  api,
                  A: [
                    location[0] + info[barIndex].xOffset,
                    location[1] +
                      info[barIndex].yOffset[params.dataIndex] -
                      info[barIndex].height[params.dataIndex]
                  ],
                  B: [
                    location[0] + info[barIndex].xOffset,
                    location[1] + info[barIndex].yOffset[params.dataIndex]
                  ],
                  C: [
                    location[0] + info[barIndex].xOffset + info[barIndex].OB[0],
                    location[1] +
                      info[barIndex].yOffset[params.dataIndex] +
                      info[barIndex].OB[1]
                  ],
                  D: [
                    location[0] + info[barIndex].xOffset + info[barIndex].OB[0],
                    location[1] +
                      info[barIndex].yOffset[params.dataIndex] -
                      info[barIndex].height[params.dataIndex] +
                      info[barIndex].OB[1]
                  ]
                },
                style: {
                  fill: info[barIndex].faceColors[1]
                }
              },
              {
                //top
                type: "quadrilateral",
                shape: {
                  api,
                  A: [
                    location[0] + info[barIndex].xOffset,
                    location[1] +
                      info[barIndex].yOffset[params.dataIndex] -
                      info[barIndex].height[params.dataIndex]
                  ],
                  B: [
                    location[0] + info[barIndex].xOffset + info[barIndex].OB[0],
                    location[1] +
                      info[barIndex].yOffset[params.dataIndex] -
                      info[barIndex].height[params.dataIndex] +
                      info[barIndex].OB[1]
                  ],
                  C: [
                    location[0] +
                      info[barIndex].xOffset +
                      info[barIndex].OB[0] +
                      info[barIndex].OC[0],
                    location[1] +
                      info[barIndex].yOffset[params.dataIndex] -
                      info[barIndex].height[params.dataIndex] +
                      info[barIndex].OB[1] +
                      info[barIndex].OC[1]
                  ],
                  D: [
                    location[0] + info[barIndex].xOffset + info[barIndex].OC[0],
                    location[1] +
                      info[barIndex].yOffset[params.dataIndex] -
                      info[barIndex].height[params.dataIndex] +
                      info[barIndex].OC[1]
                  ]
                },
                style: {
                  fill: info[barIndex].faceColors[2]
                }
              }
            ]
          };
        }
      };
      series.push(data);
    },
    //投影效果计算
    changeProject(angle, size) {
      let result = [];
      let alpha = (angle[0] * Math.PI) / 180;
      let beta = (angle[1] * Math.PI) / 180;
      let OB = [
        size[0] * Math.sin(alpha),
        -size[0] * Math.cos(alpha) * Math.sin(beta)
      ];
      let OC = [
        -size[1] * Math.cos(alpha),
        -size[1] * Math.sin(alpha) * Math.sin(beta)
      ];
      let heightRatio = Math.cos(beta);
      result.push(OB);
      result.push(OC);
      result.push(heightRatio);
      return result;
    },
    //rgba(a,b,c,d) -> a,b,c,d  #123456 -> a,b,c,d,返回的值为int类型
    getRGBA(str) {
      let datas = [];
      if (str[0] == "r") {
        if (str[3] == "a") {
          let strs = str.split(",");
          datas[0] = parseInt(strs[0].substring(5, strs[0].length));
          datas[1] = parseInt(strs[1]);
          datas[2] = parseInt(strs[2]);
          datas[3] = parseInt(strs[3].substring(0, strs[3].length - 1));
          return datas;
        } else {
          let strs = str.split(",");
          datas[0] = parseInt(strs[0].substring(4, strs[0].length));
          datas[1] = parseInt(strs[1]);
          datas[2] = parseInt(strs[2].substring(0, strs[2].length - 1));
          datas[3] = 1;
          return datas;
        }
      } else if (str[0] == "#") {
        datas[0] = parseInt(str.substring(1, 3), 16);
        datas[1] = parseInt(str.substring(3, 5), 16);
        datas[2] = parseInt(str.substring(5, 7), 16);
        datas[3] = 1;
        return datas;
      } else {
        //errorFlag
        return "error";
      }
    },
    // a,b,c,d -> rgba(a,b,c,d),返回的值为str类型
    setRGBA(datas) {
      return (
        "rgba(" +
        datas[0] +
        "," +
        datas[1] +
        "," +
        datas[2] +
        "," +
        datas[3] +
        ")"
      );
    },
    //自动生成颜色
    createBarColor(datas, angle) {
      let colors = [];
      let alpha = (angle[0] * Math.PI) / 180;
      let beta = (angle[1] * Math.PI) / 180;

      let leftdatas = [];
      let leftParam = Math.cos(alpha) * Math.cos(beta) * 0.8 + 0.2;
      leftdatas[0] = Math.round(datas[0] * leftParam);
      leftdatas[1] = Math.round(datas[1] * leftParam);
      leftdatas[2] = Math.round(datas[2] * leftParam);
      leftdatas[3] = datas[3];
      colors.push(this.setRGBA(leftdatas));

      let rightdatas = [];
      let rightParam = Math.sin(alpha) * Math.cos(beta) * 0.8 + 0.2;
      rightdatas[0] = Math.round(datas[0] * rightParam);
      rightdatas[1] = Math.round(datas[1] * rightParam);
      rightdatas[2] = Math.round(datas[2] * rightParam);
      rightdatas[3] = datas[3];
      colors.push(this.setRGBA(rightdatas));

      let topdatas = [];
      let topParam = Math.sin(beta) * 0.8 + 0.2;
      topdatas[0] = Math.round(datas[0] * topParam);
      topdatas[1] = Math.round(datas[1] * topParam);
      topdatas[2] = Math.round(datas[2] * topParam);
      topdatas[3] = datas[3];
      colors.push(this.setRGBA(topdatas));

      return colors;
    },
    createGraphBySelected(myChart, dataOption, selected) {
      //获取颜色
      let color = this.chartOption.theme.color;
      //获取数据
      let value = dataOption.staticDataValue;
      dataOption.xAxis.data = value.axisData;

      //根据stack处理数据
      //stack = { stack1: [{ name: bar1, data: [1, 2] },{ name: bar2, data: [3, 4] }] }
      let stack = {};
      //stackTemp临时存储当前bar对应的stack
      let stackTemp = "";
      for (let index in value.series) {
        //如果bar没有stack，那么自动将name作为bar的stack
        if (value.series[index].stack == "") {
          stackTemp = value.series[index].name;
        } else {
          stackTemp = value.series[index].stack;
        }
        // stackTemp未创建  初始化
        if (stack[stackTemp] == null) {
          stack[stackTemp] = [];
        }
        stack[stackTemp].push({
          name: value.series[index].name,
          data: value.series[index].data
        });
      }

      //填充颜色
      //stack = { stack1: [{ name: bar1, data: [1, 2], color: #fff },{ name: bar2, data: [3, 4], color: #fff }] }
      let colorIndex = 0;
      for (let i in stack) {
        for (let j in stack[i]) {
          if (colorIndex == color.length) {
            colorIndex -= color.length;
          }
          stack[i][j].color = color[colorIndex];
          colorIndex++;
        }
      }

      //获取堆叠图中不同柱的数据总和的最大值，以此确定所有柱的高度
      let maxValue = 0;
      for (let i in value.axisData) {
        for (let j in stack) {
          let temp = 0;
          for (let k in stack[j]) {
            if (!selected || selected[stack[j][k].name]) {
              temp += stack[j][k].data[i];
            }
          }
          if (maxValue < temp) {
            maxValue = temp;
          }
        }
      }

      //获取stackLength
      let stackLength = 0;
      for (let i in stack) {
        for (let j in stack[i]) {
          if (!selected || selected[stack[i][j].name]) {
            stackLength++;
            break;
          }
        }
      }

      let projectResult = this.changeProject(
        dataOption.projectAngle,
        dataOption.bottomSize
      );

      //创建info
      let info = [];
      //生成series
      let series = [];
      //stack的编号
      let stackIndex = 0;
      //bar的编号
      let barIndex = 0;
      for (let i in stack) {
        //初始化
        let heightTemp = [];
        for (let k in value.axisData) {
          heightTemp.push(0);
        }

        let flag = false; //判断这个stack是否有selected bar, true -> has
        for (let j in stack[i]) {
          if (!selected || selected[stack[i][j].name]) {
            flag = true;
            info.push({
              yOffset: [],
              height: []
            });
            info[barIndex].OB = projectResult[0];
            info[barIndex].OC = projectResult[1];
            info[barIndex].name = stack[i][j].name;
            info[barIndex].data = stack[i][j].data;
            info[barIndex].color = stack[i][j].color;
            info[barIndex].faceColors = this.createBarColor(
              this.getRGBA(info[barIndex].color),
              dataOption.lightAngle
            );
            info[barIndex].xOffset =
              dataOption.barSpacing * (stackIndex - (stackLength - 1) / 2);
            for (let k in value.axisData) {
              let height =
                ((dataOption.maxHeight * stack[i][j].data[k]) / maxValue) *
                projectResult[2];
              info[barIndex].height.push(height);
              info[barIndex].yOffset.push(-heightTemp[k]);
              heightTemp[k] += height;
            }
            this.addThreeDBarSeries(info, barIndex, series);
          } else {
            //生成未被选中的图
            info.push({});
            series.push({
              type: "custom",
              name: stack[i][j].name,
              color: stack[i][j].color
            });
          }
          barIndex++;
        }
        if (flag) {
          stackIndex++;
        }
      }
      let option = {
        title: dataOption.title,
        tooltip: dataOption.tooltip,
        legend: dataOption.legend,
        grid: dataOption.grid,
        xAxis: dataOption.xAxis,
        yAxis: dataOption.yAxis,
        series: series
      };

      if (!dataOption.isShowLegend) {
        option.legend = { show: false };
      } else if (selected) {
        option.legend.selected = selected;
      }

      //执行通用处理函数
      if(dataOption.customData != undefined && dataOption.customData != ""){
        try {
          option = (
          
            //里面为要处理的代码块
          eval(dataOption.customData) 
          
          )(
            option,
            dataOption.staticDataValue,
            this.$refs.chartDiv
          );
        } catch (error) {
          console.log(error)
        }
       } 
        //交互组件配置
      if(dataOption.interactData != undefined && dataOption.interactData != ""){
          try {
              option = (        
              //里面为要处理的代码块
              eval(dataOption.interactData)             
            )(
            option,
            dataOption.staticDataValue,
            this.$refs.chartDiv
            );
          } catch (error) {
            console.log(error)
          }               
       }      

      this.chart.setOption(option, true);

      //将setting存入map集合
      var setting = {
        bindingType: "threeDBar",
        option: option,
        stack: stack,
        info: info,
        param1: dataOption.barSpacing,
        param2: dataOption.maxHeight * projectResult[2]
      };

      addOption(dataOption.bindingDiv, setting);

      //开启图表联动
      if (dataOption.isLink == true) {
        this.chart.off("click");
        this.chart.on("click", params => {
          //设置参数
          let arrObject = {
            legendName: params.seriesName,
            seriesName: params.name,
            data: params.value
          };

          let arrs = JSON.stringify(arrObject);

          //获取绑定的图表
          let bindList = this.chartOption.bindList;

          if (bindList.length > 0) {
            linkChart(dataOption.arrName, arrs, bindList, this.drawingList);
          }
        });
      }
      //开启图表下钻
      else if (dataOption.isDrillDown == true) {
        this.chart.off("click");
        this.chart.on("click", params => {
          //设置参数
          let arrObject = {
            legendName: params.seriesName,
            seriesName: params.name,
            data: params.value
          };

          let arrs = JSON.stringify(arrObject);

          //获取绑定的图表
          let drillDownChartOption = this.chartOption.drillDownChartOption;

          if (
            drillDownChartOption != undefined &&
            drillDownChartOption != null
          ) {
            this.$set(
              this.chartOption.drillDownChartOption.chartOption,
              "requestParameters",
              "drillParam=" + arrs
            );
            //发送下钻消息
            VueEvent.$emit(
              "drill_down_msg",
              this.chartOption.drillDownChartOption
            );
          }
        });
      } else {
        //关闭图表联动，取消echart点击事件
        this.chart.off("click");
      }
      //开启远程图表控制
      if (dataOption.isRemote == true) {
        if (dataOption.remote != undefined && dataOption.remote != null) {
          this.chart.off("click");
          this.chart.on("click", params => {
            //设置参数
            let arrObject = {
              legendName: params.name,
              seriesName: params.seriesName,
              data: params.value
            };

            let remoteData = { ...dataOption.remote };
            remoteData.query = arrObject;
            //调用接口
            remoteChartApi(remoteData);
          });
        }
      }
    },
    //  ws连接成功，后台返回的ws数据
    receiveMsg(e) {
      if (e.action == "chat") {
        // console.log('服务端推送',e);
        this.wsValue = JSON.parse(e.result);
        this.setOptions(JSON.parse(JSON.stringify(this.chartOption)));
      }
    },
    // 建立连接、发起ws请求，以心跳方式，向服务端发送数据
    createWs() {
      this.wsObj = new WebSocket(process.env.VUE_APP_WEBSOCKET);
      // 若为对象类型，以回调方式发送
      websocketCommand(
        this.wsObj,
        "create",
        5000,
        this.chartOption.interfaceKey,
        this.sendHeartBeat,
        this.receiveMsg,
        this.reconnectWs,
        this.wsWarning
      );
    },
    // 断网重连，需要处理的逻辑
    reconnectWs() {
      if (this.wsObj) {
        console.log("%c websocket_reconnect", "color:blue");
        this.createWs();
      }
    },
    // 以回调方式向服务端发送(对象类型的)心跳
    sendHeartBeat() {
      if (this.wsTimer != "") {
        clearTimeout(this.wsTimer);
      }

      let wsTimerTask = () => {
        console.log("发送心跳", "ping");
        let obj = { action: "keep", key: this.chartOption.interfaceKey };
        this.wsObj.send(JSON.stringify(obj));
        this.wsTimer = setTimeout(() => {
          wsTimerTask();
        }, parseInt(process.env.VUE_APP_WEBSOCKET_TIMEOUT));
        this.wsWarning();
      };

      wsTimerTask();
    },
    wsWarning() {
      //如果断开连接则显示组件警告动画并清除计数器
      if (this.wsObj.readyState == 3) {
        this.animate = "warning";
        clearTimeout(this.wsTimer);
      }
    },
    closeWs() {
      clearTimeout(this.wsTimer);
      websocketCommand(
        this.wsObj,
        "close",
        5000,
        this.chartOption.interfaceKey,
        this.sendHeartBeat,
        this.receiveMsg,
        this.reconnectWs,
        this.wsWarning
      );
      this.wsObj = null;
    }
  }
};
</script>
